package com.codeworker.app.data.repository;

import android.content.Context;
import android.os.Build;

import androidx.annotation.RequiresApi;
import androidx.lifecycle.LiveData;

import com.codeworker.app.data.AccountRoomDatabase;
import com.codeworker.app.data.common.enums.PayTypeEnum;
import com.codeworker.app.data.dao.BillDao;
import com.codeworker.app.data.dao.DailyRecordDao;
import com.codeworker.app.data.dao.MonthlyRecordDao;
import com.codeworker.app.data.dao.PropertyDao;
import com.codeworker.app.data.entity.Bill;
import com.codeworker.app.data.entity.DailyRecord;
import com.codeworker.app.data.entity.MonthlyRecord;
import com.codeworker.app.data.entity.Property;
import com.codeworker.app.data.entity.Record;
import com.codeworker.app.data.entity.vo.BillData;
import com.codeworker.app.utils.FormatUtil;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class BillRepository {

    private final BillDao billDao;
    private final DailyRecordDao dailyRecordDao;
    private final MonthlyRecordDao monthlyRecordDao;
    private final PropertyDao propertyDao;
    private final DailyRecord dailyRecord;
    private final MonthlyRecord monthlyRecord;
    private final List<BillData> billData;
    private final List<MonthlyRecord> monthlyRecordList;
    private final LiveData<MonthlyRecord> monthlyRecordDataList;

    public BillRepository(Context context) {
        AccountRoomDatabase db = AccountRoomDatabase.getDatabase(context);

        billDao = db.billDao();
        dailyRecordDao = db.dailyRecordDao();
        monthlyRecordDao = db.monthlyRecordDao();
        propertyDao = db.propertyDao();

        LocalDate localDate = LocalDate.now();
        int year = localDate.getYear();
        int month = localDate.getMonthValue();
        int day = localDate.getDayOfMonth();
        billData = getBillData(month, year);
        dailyRecord = dailyRecordDao.getDailyRecordByDMY(day, month, year);
        monthlyRecordDataList = getMonthlyRecordData(month, year);
        monthlyRecordList = getMonthlyRecordList(year);
        monthlyRecord = getMonthlyRecord(month, year);
    }

    public List<MonthlyRecord> getMonthlyRecordList() {
        return monthlyRecordList;
    }

    public LiveData<MonthlyRecord> getMonthlyRecord() {
        return monthlyRecordDataList;
    }

    public LiveData<MonthlyRecord> getMonthlyRecordData(int month, int year) {
        return monthlyRecordDao.getMonthlyRecordDataByMonthAndYear(month, year);
    }

    public LiveData<DailyRecord> getDailyRecordData(int day, int month, int year) {
        return dailyRecordDao.getDailyRecordDataByDMY(day, month, year);
    }

    public MonthlyRecord getMonthlyRecord(int month, int year) {
        return monthlyRecordDao.getMonthlyRecordByMonthAndYear(month, year);
    }

    public List<BillData> getBillData() {
        return billData;
    }

    public List<Bill> getMonthlyBill(int month, int year) {
        return billDao.getBillListByMonth(month, year);
    }

    public LiveData<List<Bill>> getDailyBillList(int day, int month, int year) {
        return billDao.getBillLiveData(day, month, year);
    }

    public List<BillData> getBillData(int month, int year) {
        return dailyRecordDao.getDailyRecordListByMonth(month, year)
                .stream()
                .map(record -> {
                    BillData billData = new BillData();
                    billData.setDailyRecord(record);
                    billData.setDailyBills(billDao.getBillListByDMY(record.getDay(), month, year));
                    return billData;
                }).collect(Collectors.toList());
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    public void insert(Bill bill) {
        AccountRoomDatabase.databaseWriteExecutor.execute(() -> {
            billDao.insert(bill);
            //判断是否是收入
            boolean isIncome = PayTypeEnum.INCOME.getPayTypeInDB().equals(bill.getPayType());
            //收支金额
            int billValue = bill.getValue();
            //插入每日收支记录表
            insertRecord(billValue, isIncome, dailyRecord, DailyRecord::new, dailyRecordDao::insert);
            //插入每月收支记录表
            insertRecord(billValue, isIncome, monthlyRecord, MonthlyRecord::new, monthlyRecordDao::insert);
            //更新资产表
            updateProperty(bill.getPropertyId(), isIncome, billValue);
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    public void update(Bill newBill, Bill oldBill) {
        AccountRoomDatabase.databaseWriteExecutor.execute(() -> {
            billDao.update(newBill);
            //将旧数据减去金额，将新数据金额写入(由于新旧数据的收支类型不一定相同,不能直接做金额加减)
            updateOrDeleteRecord(oldBill, -oldBill.getValue());
            updateOrDeleteRecord(newBill, newBill.getValue());
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    public void delete(Bill bill) {
        AccountRoomDatabase.databaseWriteExecutor.execute(() -> {
            billDao.delete(bill);
            //收支金额，因为是删除操作，这里是减去金额
            updateOrDeleteRecord(bill, -bill.getValue());
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void updateOrDeleteRecord(Bill bill, int billValue) {
        //判断是否是收入
        boolean isIncome = PayTypeEnum.INCOME.getPayTypeInDB().equals(bill.getPayType());
        //时间转换
        LocalDateTime timeConverter = LocalDateTime.parse(bill.getCreateTime(), FormatUtil.formatter);
        //更新每日收支记录表
        updateRecord(billValue, isIncome, timeConverter, true,
                (t, b) -> (DailyRecord) this.getRecord(t, b), dailyRecordDao::update);
        //更新每月收支记录表
        updateRecord(billValue, isIncome, timeConverter, false,
                (t, b) -> (MonthlyRecord) this.getRecord(t, b), monthlyRecordDao::update);
        //更新资产表
        updateProperty(bill.getPropertyId(), isIncome, billValue);
    }

    private <T extends Record> void insertRecord(int billValue, boolean isIncome, T record,
                                                 Supplier<T> supplier, Consumer<T> consumer) {
        // 如果还没有记录数据，先初始化
        if (Objects.isNull(record)) {
            record = supplier.get();
            record.setIncome(isIncome ? billValue : 0);
            record.setExpend(isIncome ? 0 : billValue);
            consumer.accept(record);
            return;
        }
        // 如果已存在记录数据
        setRecordData(isIncome, record, billValue);
        consumer.accept(record);
    }

    private <T extends Record> void updateRecord(int billValue, boolean isIncome, LocalDateTime time,
                                                 boolean isDailyRecord,
                                                 BiFunction<LocalDateTime, Boolean, T> function,
                                                 Consumer<T> consumer) {
        T record = function.apply(time, isDailyRecord);
        setRecordData(isIncome, record, billValue);
        consumer.accept(record);
    }

    private Record getRecord(LocalDateTime time, boolean isDailyRecord) {
        return isDailyRecord
                ? dailyRecordDao.getDailyRecordByDMY(time.getDayOfMonth(), time.getMonthValue(), time.getYear())
                : monthlyRecordDao.getMonthlyRecordByMonthAndYear(time.getMonthValue(), time.getYear());
    }

    /**
     * 更新资产数据
     *
     * @param propertyId 资产id
     * @param isIncome   是否收入
     * @param billValue  收支金额
     */
    private void updateProperty(int propertyId, boolean isIncome, int billValue) {
        Property property = propertyDao.getPropertyById(propertyId);
        int propertyValue = property.getValue();
        property.setValue(isIncome ? propertyValue + billValue : propertyValue - billValue);
        propertyDao.update(property);
    }

    private void setRecordData(boolean isIncome, Record record, int bill) {
        //如果是收入
        if (isIncome) {
            record.setIncome(record.getIncome() + bill);
            return;
        }
        //支出情况
        record.setExpend(record.getExpend() + bill);
    }

    public List<MonthlyRecord> getMonthlyRecordList(int year) {
        return monthlyRecordDao.getMonthlyRecordListByYear(year);
    }

}
